/**
 * @file svg path转换为轮廓
 * @author mengke01(kekee000@gmail.com)
 */

import bezierCubic2Q2 from '../../math/bezierCubic2Q2';
import getArc from '../../graphics/getArc';
import parseParams from './parseParams';

/**
 * 三次贝塞尔曲线，转二次贝塞尔曲线
 *
 * @param {Array} cubicList 三次曲线数组
 * @param {Array} contour 当前解析后的轮廓数组
 * @return {Array} 当前解析后的轮廓数组
 */
function cubic2Points(cubicList, contour) {

    let i;
    let l;
    const q2List = [];

    cubicList.forEach(c => {
        const list = bezierCubic2Q2(c[0], c[1], c[2], c[3]);
        for (i = 0, l = list.length; i < l; i++) {
            q2List.push(list[i]);
        }
    });

    let q2;
    let prevq2;
    for (i = 0, l = q2List.length; i < l; i++) {
        q2 = q2List[i];
        if (i === 0) {
            contour.push({
                x: q2[1].x,
                y: q2[1].y
            });
            contour.push({
                x: q2[2].x,
                y: q2[2].y,
                onCurve: true
            });
        }
        else {
            prevq2 = q2List[i - 1];
            // 检查是否存在切线点
            if (
                prevq2[1].x + q2[1].x === 2 * q2[0].x
                && prevq2[1].y + q2[1].y === 2 * q2[0].y
            ) {
                contour.pop();
            }
            contour.push({
                x: q2[1].x,
                y: q2[1].y
            });
            contour.push({
                x: q2[2].x,
                y: q2[2].y,
                onCurve: true
            });
        }
    }

    contour.push({
        x: q2[2].x,
        y: q2[2].y,
        onCurve: true
    });

    return contour;
}


/**
 * svg 命令数组转轮廓
 *
 * @param {Array} segments svg 命令数组
 * @return {Array} 轮廓数组
 */
function segments2Contours(segments) {

    // 解析segments
    const contours = [];
    let contour = [];
    let prevX = 0;
    let prevY = 0;
    let segment;
    let args;
    let cmd;
    let relative;
    let q;
    let ql;
    let px;
    let py;
    let cubicList;
    let p1;
    let p2;
    let c1;
    let c2;
    let prevCubicC1; // 三次贝塞尔曲线前一个控制点，用于绘制`s`命令

    for (let i = 0, l = segments.length; i < l; i++) {
        segment = segments[i];
        cmd = segment.cmd;
        relative = segment.relative;
        args = segment.args;

        if (args && !args.length && cmd !== 'Z') {
            console.warn('`' + cmd + '` command args empty!');
            continue;
        }

        if (cmd === 'Z') {
            contours.push(contour);
            contour = [];
        }
        else if (cmd === 'M' || cmd === 'L') {
            if (args.length % 2) {
                throw new Error('`M` command error:' + args.join(','));
            }

            // 这里可能会连续绘制，最后一个是终点
            if (relative) {
                px = prevX;
                py = prevY;
            }
            else {
                px = 0;
                py = 0;
            }

            for (q = 0, ql = args.length; q < ql; q += 2) {

                if (relative) {
                    px += args[q];
                    py += args[q + 1];
                }
                else {
                    px = args[q];
                    py = args[q + 1];
                }

                contour.push({
                    x: px,
                    y: py,
                    onCurve: true
                });
            }

            prevX = px;
            prevY = py;
        }
        else if (cmd === 'H') {
            if (relative) {
                prevX += args[0];
            }
            else {
                prevX = args[0];
            }

            contour.push({
                x: prevX,
                y: prevY,
                onCurve: true
            });
        }
        else if (cmd === 'V') {
            if (relative) {
                prevY += args[0];
            }
            else {
                prevY = args[0];
            }

            contour.push({
                x: prevX,
                y: prevY,
                onCurve: true
            });
        }
        // 二次贝塞尔
        else if (cmd === 'Q') {
            // 这里可能会连续绘制，最后一个是终点
            if (relative) {
                px = prevX;
                py = prevY;
            }
            else {
                px = 0;
                py = 0;
            }

            for (q = 0, ql = args.length; q < ql; q += 4) {

                contour.push({
                    x: px + args[q],
                    y: py + args[q + 1]
                });
                contour.push({
                    x: px + args[q + 2],
                    y: py + args[q + 3],
                    onCurve: true
                });

                if (relative) {
                    px += args[q + 2];
                    py += args[q + 3];
                }
                else {
                    px = 0;
                    py = 0;
                }
            }

            if (relative) {
                prevX = px;
                prevY = py;
            }
            else {
                prevX = args[ql - 2];
                prevY = args[ql - 1];
            }
        }
        // 二次贝塞尔平滑
        else if (cmd === 'T') {
            // 这里需要移除上一个曲线的终点
            let last = contour.pop();
            let pc = contour[contour.length - 1];
            if (!pc) {
                pc = last;
            }

            contour.push(pc = {
                x: 2 * last.x - pc.x,
                y: 2 * last.y - pc.y
            });

            px = prevX;
            py = prevY;

            for (q = 0, ql = args.length - 2; q < ql; q += 2) {

                if (relative) {
                    px += args[q];
                    py += args[q + 1];
                }
                else {
                    px = args[q];
                    py = args[q + 1];
                }

                last = {
                    x: px,
                    y: py
                };

                contour.push(pc = {
                    x: 2 * last.x - pc.x,
                    y: 2 * last.y - pc.y
                });
            }

            if (relative) {
                prevX = px + args[ql];
                prevY = py + args[ql + 1];
            }
            else {
                prevX = args[ql];
                prevY = args[ql + 1];
            }

            contour.push({
                x: prevX,
                y: prevY,
                onCurve: true
            });

        }
        // 三次贝塞尔
        else if (cmd === 'C') {
            if (args.length % 6) {
                throw new Error('`C` command params error:' + args.join(','));
            }

            // 这里可能会连续绘制，最后一个是终点
            cubicList = [];

            if (relative) {
                px = prevX;
                py = prevY;
            }
            else {
                px = 0;
                py = 0;
            }

            p1 = {
                x: prevX,
                y: prevY
            };

            for (q = 0, ql = args.length; q < ql; q += 6) {

                c1 = {
                    x: px + args[q],
                    y: py + args[q + 1]
                };

                c2 = {
                    x: px + args[q + 2],
                    y: py + args[q + 3]
                };

                p2 = {
                    x: px + args[q + 4],
                    y: py + args[q + 5]
                };

                cubicList.push([p1, c1, c2, p2]);

                p1 = p2;

                if (relative) {
                    px += args[q + 4];
                    py += args[q + 5];
                }
                else {
                    px = 0;
                    py = 0;
                }
            }

            if (relative) {
                prevX = px;
                prevY = py;
            }
            else {
                prevX = args[ql - 2];
                prevY = args[ql - 1];
            }

            cubic2Points(cubicList, contour);
            prevCubicC1 = cubicList[cubicList.length - 1][2];
        }
        // 三次贝塞尔平滑
        else if (cmd === 'S') {
            if (args.length % 4) {
                throw new Error('`S` command params error:' + args.join(','));
            }

            // 这里可能会连续绘制，最后一个是终点
            cubicList = [];

            if (relative) {
                px = prevX;
                py = prevY;
            }
            else {
                px = 0;
                py = 0;
            }

            // 这里需要移除上一个曲线的终点
            p1 = contour.pop();
            if (!prevCubicC1) {
                prevCubicC1 = p1;
            }

            c1 = {
                x: 2 * p1.x - prevCubicC1.x,
                y: 2 * p1.y - prevCubicC1.y
            };

            for (q = 0, ql = args.length; q < ql; q += 4) {

                c2 = {
                    x: px + args[q],
                    y: py + args[q + 1]
                };

                p2 = {
                    x: px + args[q + 2],
                    y: py + args[q + 3]
                };

                cubicList.push([p1, c1, c2, p2]);

                p1 = p2;

                c1 = {
                    x: 2 * p1.x - c2.x,
                    y: 2 * p1.y - c2.y
                };

                if (relative) {
                    px += args[q + 2];
                    py += args[q + 3];
                }
                else {
                    px = 0;
                    py = 0;
                }
            }

            if (relative) {
                prevX = px;
                prevY = py;
            }
            else {
                prevX = args[ql - 2];
                prevY = args[ql - 1];
            }

            cubic2Points(cubicList, contour);
            prevCubicC1 = cubicList[cubicList.length - 1][2];
        }
        // 求弧度, rx, ry, angle, largeArc, sweep, ex, ey
        else if (cmd === 'A') {
            if (args.length % 7) {
                throw new Error('arc command params error:' + args.join(','));
            }

            for (q = 0, ql = args.length; q < ql; q += 7) {
                let ex = args[q + 5];
                let ey = args[q + 6];

                if (relative) {
                    ex = prevX + ex;
                    ey = prevY + ey;
                }

                const path = getArc(
                    args[q], args[q + 1],
                    args[q + 2], args[q + 3], args[q + 4],
                    {x: prevX, y: prevY},
                    {x: ex, y: ey}
                );

                if (path && path.length > 1) {
                    for (let r = 1, rl = path.length; r < rl; r++) {
                        contour.push(path[r]);
                    }
                }
                prevX = ex;
                prevY = ey;
            }
        }
    }

    return contours;
}

/**
 * svg path转轮廓
 *
 * @param {string} path svg的path字符串
 * @return {Array} 转换后的轮廓
 */
export default function path2contours(path) {

    if (!path || !path.length) {
        return null;
    }

    path = path.trim();

    // 修正头部不为`m`的情况
    if (path[0] !== 'M' && path[0] !== 'm') {
        path = 'M 0 0' + path;
    }

    // 修复中间没有结束符`z`的情况
    path = path.replace(/(\d+)\s*(m|$)/gi, '$1z$2');

    // 获取segments
    const segments = [];
    let cmd;
    let relative = false;
    let lastIndex;
    let args;

    for (let i = 0, l = path.length; i < l; i++) {
        const c = path[i].toUpperCase();
        const r = c !== path[i];

        switch (c) {
        case 'M':
            /* jshint -W086 */
            if (i === 0) {
                cmd = c;
                lastIndex = 1;
                break;
            }
        // eslint-disable-next-line no-fallthrough
        case 'Q':
        case 'T':
        case 'C':
        case 'S':
        case 'H':
        case 'V':
        case 'L':
        case 'A':
        case 'Z':
            if (cmd === 'Z') {
                segments.push({cmd: 'Z'});
            }
            else {
                args = path.slice(lastIndex, i);
                segments.push({
                    cmd,
                    relative,
                    args: parseParams(args)
                });
            }

            cmd = c;
            relative = r;
            lastIndex = i + 1;
            break;

        }
    }

    segments.push({cmd: 'Z'});

    return segments2Contours(segments);
}
